use super::reader::Reader;

#[derive(Debug)]
pub enum Event<'l> {
	Error(Error, usize, usize),

	Start(&'l str),
	End(&'l str),
	Attr(&'l str, &'l str),
	Text(&'l str),
	Comment(&'l str),
	Declaration(&'l str),
	Instruction(&'l str),
}

pub struct Parser<'l> {
	r: Reader<'l>,
	tag: Option<&'l str>,
}
impl<'l> Parser<'l> {
	#[inline]
	pub fn new(content: &'l str) -> Self {
		Parser {
			r: Reader::new(content),
			tag: None,
		}
	}
	pub fn pos(&self) -> (usize, usize) {
		self.r.pos()
	}
	fn read_next(&mut self) -> Result<Event<'l>, Error> {
		if let Some(name) = self.tag {
			match self.read_attribute() {
				Ok((n, v)) => Ok(Event::Attr(n, v)),
				Err(Error::Empty) => self.read_tag_end(name),
				Err(error) => Err(error),
			}
		} else {
			match self.next_text() {
				Ok(event) => Ok(event),
				Err(Error::Empty) => self.next_angle(),
				Err(error) => Err(error),
			}
		}
	}
	fn next_angle(&mut self) -> Result<Event<'l>, Error> {
		let content: String = self.r.peek_many().take(4).collect();
		if content.is_empty() {
			Err(Error::Eof)
		} else if content.starts_with("<!--") {
			self.read_comment()
		} else if content.starts_with("<!") {
			self.read_declaration()
		} else if content.starts_with("<?") {
			self.read_instruction()
		} else if content.starts_with("</") {
			self.read_end_tag()
		} else if content.starts_with('<') {
			self.read_tag_start()
		} else {
			Err(Error::Msg("found an unknown sequence"))
		}
	}
	fn next_text(&mut self) -> Result<Event<'l>, Error> {
		self.r.capture(|r| r.eat_until_char('<')).map(Event::Text).ok_or(Error::Empty)
	}
	fn read_comment(&mut self) -> Result<Event<'l>, Error> {
		let content = self.r.capture(|r| r.xml_comment()).ok_or(Error::Msg("found a malformed comment"))?;
		Ok(Event::Comment(content))
	}
	fn read_declaration(&mut self) -> Result<Event<'l>, Error> {
		let content = self
			.r
			.capture(|r| r.xml_declaration())
			.ok_or(Error::Msg("found a malformed declaration"))?;
		Ok(Event::Declaration(content))
	}
	fn read_instruction(&mut self) -> Result<Event<'l>, Error> {
		let content = self
			.r
			.capture(|r| r.xml_instruction())
			.ok_or(Error::Msg("found a malformed instruction"))?;
		Ok(Event::Instruction(content))
	}
	fn read_end_tag(&mut self) -> Result<Event<'l>, Error> {
		self.r.eat_char('<').then_some("").ok_or(Error::Msg("expect <"))?;
		self.r.eat_char('/').then_some("").ok_or(Error::Msg("expect /"))?;
		let name = self.r.capture(|r| r.xml_name()).ok_or(Error::Msg("expected a name"))?;
		self.r.eat_char('>').then_some("").ok_or(Error::Msg("expect >"))?;
		Ok(Event::End(name))
	}
	fn read_tag_end(&mut self, name: &'l str) -> Result<Event<'l>, Error> {
		self.r.xml_whitespace();
		match self.r.peek() {
			Some('/') => {
				self.r.eat_char('/');
				match self.r.peek() {
					Some('>') => {
						self.r.eat_char('>');
						self.tag = None;
						return Ok(Event::End(name));
					}
					_ => return Err(Error::Msg("expecte tag end")),
				}
			}
			Some('>') => {
				self.r.eat_char('>');
				self.tag = None;
				self.next_text().or_else(|_| self.next_angle())
			}
			_ => return Err(Error::Msg("expecte tag end")),
		}
	}
	fn read_tag_start(&mut self) -> Result<Event<'l>, Error> {
		self.r.eat_char('<').then_some("").ok_or(Error::Msg("expect <"))?;
		let name = self.r.capture(|r| r.xml_name()).ok_or(Error::Msg("expected a name"))?;
		self.tag = Some(name);
		Ok(Event::Start(name))
	}
	fn read_attribute(&mut self) -> Result<(&'l str, &'l str), Error> {
		self.r.xml_whitespace();
		let name = self.r.capture(|r| r.xml_name()).ok_or(Error::Empty)?;
		self.r.xml_equality().then_some("").ok_or(Error::Msg("expect eq"))?;
		let value = self
			.r
			.capture(|r| r.xml_attribute_value())
			.and_then(|v| v.get(1..v.len().saturating_sub(1)))
			.ok_or(Error::Msg("expect value"))?;
		Ok((name, value))
	}
}
impl<'l> Iterator for Parser<'l> {
	type Item = Event<'l>;
	fn next(&mut self) -> Option<Self::Item> {
		match self.read_next() {
			Ok(event) => Some(event),
			Err(Error::Eof) => None,
			Err(error) => {
				let (r, c) = self.pos();
				Some(Event::Error(error, r, c))
			}
		}
	}
}

#[derive(Debug, Clone, PartialEq)]
pub enum Error {
	Eof,
	Empty,
	Msg(&'static str),
}
impl core::error::Error for Error {}
impl core::fmt::Display for Error {
	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
		match self {
			Error::Eof => f.write_str("Eof"),
			Error::Empty => f.write_str("Empty"),
			Error::Msg(msg) => f.write_str(msg),
		}
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	#[test]
	fn parse_xml() {
		let p = Parser::new(
			r#"
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
	<key>creator</key>
	<single/>
</plist>"#,
		);
		for e in p {
			println!("{:?}", e);
		}
	}
	#[test]
	fn next_tag() {
		fn test0(content: &str, value: &str) {
			let mut parser = Parser::new(content);
			match parser.next().unwrap() {
				Event::Start(v) => assert_eq!(v, value),
				_ => unreachable!(),
			}
		}
		test0("<foo>", "foo");
		test0("<foo/>", "foo");
		test0("  <foo/>", "foo");
		test0("  <foo xx/>", "foo");
		test0("  <foo xx/>", "foo");
	}
	#[test]
	fn next_text() {
		fn test0(content: &str, value: &str) {
			let mut parser = Parser::new(content);
			match parser.next().unwrap() {
				Event::Text(v) => assert_eq!(v, value),
				_ => unreachable!(),
			}
		}
		test0("foo <bar>", "foo");
		test0("  foo<bar>", "foo");
		test0("foo> <bar>", "foo>");
	}
	#[test]
	fn doctype() {
		let content = r#"
<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
               "http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
  <!ENTITY % local.common.attrib "xmlns:xi  CDATA  #FIXED 'http://www.w3.org/2003/XInclude'">
  <!ENTITY version SYSTEM "version.xml">
]>
<book id="index">
</book>
"#;
		let r = Parser::new(&content);
		for e in r {
			match e {
				Event::Error(_, _, _) => panic!(""),
				_ => {}
			}
		}
	}
}

pub mod xml_reader {
	use super::*;

	impl<'a> Reader<'a> {
		// https://www.w3.org/TR/REC-xml/#NT-S
		#[inline]
		pub fn xml_whitespace(&mut self) -> bool {
			self.eat_any("\x20\x09\x0D\x0A")
		}
		// https://www.w3.org/TR/REC-xml/#NT-Char
		#[inline]
		pub fn xml_character(&mut self) -> bool {
			self.eat_if(check_character)
		}
		// https://www.w3.org/TR/REC-xml/#NT-NameChar
		#[inline]
		pub fn xml_name_character(&mut self) -> bool {
			self.eat_if(check_name_character)
		}
		// https://www.w3.org/TR/REC-xml/#NT-NameStartChar
		#[inline]
		pub fn xml_name_start_character(&mut self) -> bool {
			self.eat_if(check_name_start_character)
		}
		// https://www.w3.org/TR/REC-xml/#NT-Name
		pub fn xml_name(&mut self) -> bool {
			self.xml_name_start_character() && {
				while self.xml_name_character() {}
				true
			}
		}
		// https://www.w3.org/TR/REC-xml/#NT-Reference
		pub fn xml_reference(&mut self) -> bool {
			self.eat_char('&')
				&& if self.eat_char('#') {
					if self.eat_char('x') {
						self.xml_digits_hex()
					} else {
						self.xml_digits()
					}
				} else {
					self.xml_name()
				} && self.eat_char(';')
		}
		#[inline]
		pub fn xml_digits(&mut self) -> bool {
			self.eat_while(|c| c.is_ascii_digit())
		}
		#[inline]
		pub fn xml_digits_hex(&mut self) -> bool {
			self.eat_while(|c| c.is_ascii_digit() || ('a'..='f').contains(&c) || ('A'..='F').contains(&c))
		}
		// https://www.w3.org/TR/REC-xml/#NT-Attribute
		pub fn xml_attribute(&mut self) -> bool {
			self.xml_name() && self.xml_equality() && self.xml_attribute_value()
		}
		// https://www.w3.org/TR/REC-xml/#NT-Eq
		pub fn xml_equality(&mut self) -> bool {
			self.xml_whitespace();
			let consumed = self.eat_char('=');
			self.xml_whitespace();
			consumed
		}
		// https://www.w3.org/TR/REC-xml/#NT-AttValue
		pub fn xml_attribute_value(&mut self) -> bool {
			let single;
			if self.eat_char('\'') {
				single = true;
			} else if self.eat_char('"') {
				single = false;
			} else {
				return false;
			}
			loop {
				self.eat_until_any(if single { "<&'" } else { "<&\"" });
				match self.peek() {
					Some('&') => {
						if !self.xml_reference() {
							return false;
						}
					}
					_ => break,
				}
			}
			self.eat_char(if single { '\'' } else { '"' })
		}
		pub fn xml_tag(&mut self) -> bool {
			self.eat_char('<') && self.eat_until_char('>') && self.eat_char('>')
		}
		// https://www.w3.org/TR/REC-xml/#NT-PI
		pub fn xml_instruction(&mut self) -> bool {
			self.eat_char('<') && self.eat_char('?') && self.eat_until_char('>') && self.eat_char('>')
		}
		// https://www.w3.org/TR/REC-xml/#NT-XMLDecl
		pub fn xml_declaration(&mut self) -> bool {
			self.eat_char('<')
				&& self.eat_char('!')
				&& {
					self.eat_until_any("[>");
					if Some('[') == self.peek() {
						self.eat_until_char(']');
						self.eat_char(']');
						self.eat_until_char('>');
					}
					true
				} && self.eat_char('>')
		}
		// https://www.w3.org/TR/REC-xml/#sec-comments
		pub fn xml_comment(&mut self) -> bool {
			self.eat_char('<')
				&& self.eat_char('!')
				&& self.eat_char('-')
				&& self.eat_char('-')
				&& {
					while let Some(c) = self.peek() {
						if c == '-' {
							match self.peek_many().nth(1).map(|c| c != '-' && check_character(c)) {
								Some(true) => self.eat_char('-'),
								_ => break,
							};
						} else if !self.xml_character() {
							break;
						}
					}
					true
				} && self.eat_char('-')
				&& self.eat_char('-')
				&& self.eat_char('>')
		}
	}
	// https://www.w3.org/TR/REC-xml/#NT-Char
	fn check_character(target: char) -> bool {
		matches!(
			target,
			'\u{9}'
			| '\u{A}'
			| '\u{D}'
			| '\u{20}'..='\u{D7FF}'
			| '\u{E000}'..='\u{FFFD}'
			| '\u{10000}'..='\u{10FFFF}',
		)
	}
	// https://www.w3.org/TR/REC-xml/#NT-NameChar
	fn check_name_character(target: char) -> bool {
		if check_name_start_character(target) {
			return true;
		}
		matches!(
			target,
			'-'
			| '.'
			| '0'..='9'
			| '\u{B7}'
			| '\u{0300}'..='\u{036F}'
			| '\u{203F}'..='\u{2040}',
		)
	}
	// https://www.w3.org/TR/REC-xml/#NT-NameStartChar
	fn check_name_start_character(target: char) -> bool {
		matches!(
			target,
			':'
			| 'A'..='Z'
			| '_'
			| 'a'..='z'
			| '\u{C0}'..='\u{D6}'
			| '\u{D8}'..='\u{F6}'
			| '\u{F8}'..='\u{2FF}'
			| '\u{370}'..='\u{37D}'
			| '\u{37F}'..='\u{1FFF}'
			| '\u{200C}'..='\u{200D}'
			| '\u{2070}'..='\u{218F}'
			| '\u{2C00}'..='\u{2FEF}'
			| '\u{3001}'..='\u{D7FF}'
			| '\u{F900}'..='\u{FDCF}'
			| '\u{FDF0}'..='\u{FFFD}'
			| '\u{10000}'..='\u{EFFFF}',
		)
	}

	#[cfg(test)]
	mod tests {
		use super::Reader;
		#[test]
		fn capture() {
			let mut reader = Reader::new("abcdefg");
			assert!(reader.eat_any("ab"));
			let content = reader.capture(|reader| reader.eat_any("cde"));
			assert_eq!(content.unwrap(), "cde");
		}
		#[test]
		fn consume_attribute() {
			fn test0(content: &str) {
				assert!(Reader::new(content).xml_attribute())
			}
			test0("foo='bar'");
			test0("foo = \t 'bar'");
			test0("foo= \"bar\"");
			test0("標籤='數值'");
			test0("foo='&bar;'");
			test0("foo='bar &buz;'");
			test0("foo='bar &buz; qux'");

			fn test1(content: &str) {
				assert!(!Reader::new(content).xml_attribute())
			}
			test1("foo");
			test1("foo bar");
			test1("foo=bar");
			test1("foo='bar");
			test1("foo=\"bar");
			test1("foo='&bar'");
			test1("foo='bar &bar'");
			test1("foo='bar &bar qux'");
		}
		#[test]
		fn consume_comment() {
			fn test0(content: &str, value: &str) {
				let mut reader = Reader::new(content);
				let v = reader.capture(|reader| reader.xml_comment());
				assert_eq!(v.unwrap(), value);
			}
			test0("<!-- foo --> bar", "<!-- foo -->");
			test0("<!-- foo > --> bar", "<!-- foo > -->");

			fn test1(content: &str) {
				let mut reader = Reader::new(content);
				assert!(!reader.xml_comment());
			}
			// https://www.w3.org/TR/REC-xml/#sec-comments
			test1("<!-- B+, B, or B--->");
		}
		#[test]
		fn consume_name() {
			fn test0(content: &str, value: &str) {
				let mut reader = Reader::new(content);
				let v = reader.capture(|reader| reader.xml_name());
				assert_eq!(v.unwrap(), value);
			}
			test0("foo", "foo");
			test0("foo bar", "foo");
			test0("foo42 bar", "foo42");
			test0("foo-bar baz", "foo-bar");
			test0("foo/", "foo");

			fn test1(content: &str) {
				let mut reader = Reader::new(content);
				assert!(!reader.xml_name());
			}
			test1(" foo");
			test1("!foo");
			test1("<foo");
			test1("?foo");
		}
		#[test]
		fn consume_reference() {
			fn test0(content: &str, value: &str) {
				let mut reader = Reader::new(content);
				let v = reader.capture(|reader| reader.xml_reference());
				assert_eq!(v.unwrap(), value);
			}
			test0("&#42; foo", "&#42;");
			test0("&#x42aB; foo", "&#x42aB;");
			test0("&foo; bar", "&foo;");

			fn test1(content: &str) {
				let mut reader = Reader::new(content);
				assert!(!reader.xml_reference());
			}
			test1(" &#42; foo");
			test1("#42; foo");
			test1("&42; foo");
			test1("&#42 foo");
			test1("&#x42z; foo");
			test1("&foo bar");
			test1("foo; bar");
		}
		#[test]
		fn consume_whitespace() {
			let mut reader = Reader::new(" \t  \n\n  \tm ");
			reader.xml_whitespace();
			assert_eq!(reader.pos(), (3, 4));
		}
	}
}
